﻿using iTool.Clustering.Center;
using iTool.Cloud.Center.Model;
using iTool.Common;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Configuration;
using Orleans.Hosting;
using Orleans.Runtime;
using Orleans.Streams;
using System;
using System.Threading.Tasks;
using iTool.Cloud.Center;
using iTool.Cloud.Center.Logger;

namespace iTool.ClusterComponent
{
    public interface iToolClusterHostClient : IClusterService
    {
        IQueueProvider GetQueueProvider(string name);
    }

    public class ClusterHostClient : iToolClusterHostClient
    {
        IClusterClient iClient;
        IServiceProvider ServiceProvider;
        public ClusterHostClient(IClusterClient client, string clusterId)
        {
            this.iClient = client;
            this.ServiceProvider = client.ServiceProvider;

            var serviceCollection = new ServiceCollection();
            serviceCollection.AddSingleton<IClusterClient>(this.ServiceProvider.GetService<IClusterClient>());
            serviceCollection.AddSingleton<IClusterService>(this);
            serviceCollection.AddSingleton<iToolClusterHostClient>(this);
            serviceCollection.AddSingleton<iCenterClusterClient>(this.ServiceProvider.GetService<iCenterClusterClient>());
            iBox.RegisterServiceProvider(serviceCollection.BuildServiceProvider(), "IClusterService");
        }

        public T GetService<T>(Guid primaryKey) where T : iToolServiceWithGuidKey
        {
            return this.iClient.GetGrain<T>(primaryKey);
        }

        public T GetService<T>(long primaryKey) where T : iToolServiceWithIntegerKey
        {
            return this.iClient.GetGrain<T>(primaryKey);
        }

        public T GetService<T>(string primaryKey) where T : iToolServiceWithStringKey
        {
            return this.iClient.GetGrain<T>(primaryKey);
        }

        public T GetService<T>(long primaryKey1, string primaryKey2) where T : iToolServiceWithIntegerCompoundKey
        {
            return this.iClient.GetGrain<T>(primaryKey1, primaryKey2);
        }

        public T GetService<T>(Guid primaryKey1, string primaryKey2) where T : iToolServiceWithGuidCompoundKey
        {
            return this.iClient.GetGrain<T>(primaryKey1, primaryKey2);
        }

        /// <summary>
        /// 获队列
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public IQueueProvider GetQueueProvider(string name)
        {
            IStreamProvider stream =  this.iClient.GetStreamProvider(name);
            return new QueueProvider(stream);
        }

        public T GetServiceByClusterNoder<T>(string targetHost) where T : iToolServiceWithNoder
        {
            return this.iClient.GetGrain<T>(targetHost);
        }
        public IManagement GetManagementer() => new ManagementService(this.iClient.GetGrain<IManagementGrain>(0));

        public T GetService<T>() where T : iToolService
        {
            return this.iClient.GetGrain<T>(0);
        }

        public async Task StopAsync()
        {
            await this.iClient.DisposeAsync();
        }

        public T GetInterface<T>()
        {
            return this.ServiceProvider.GetService<T>();
        }
    }

    public class iToolClientBuilder
    {

        readonly ClientBuilder builder;
        const int InitializeAttemptsBeforeFailing = 180;
        private int attempt = 0;
        private string clusterId { get; set; }
        private Action anavailable;
        private const string Invariant = "System.Data.SqlClient";

        public iToolClientBuilder()
        {
            this.builder = new ClientBuilder();
            this.Default();
            this.AddFromAppDomain();
        }

        void Default()
        {
            this.builder.AddSimpleMessageStreamProvider("iToolSimpleStream", (SimpleMessageStreamProviderOptions opt) =>
            {
                opt.FireAndForgetDelivery = true;
                opt.OptimizeForImmutableData = true;
                opt.PubSubType = StreamPubSubType.ExplicitGrainBasedAndImplicit;
            });
        }

        public iToolClientBuilder UseiCenterClustering(ClusterIdentificationOptions identificationOptions, ClusteringStaticOptions configureOptions)
        {
            this.builder.Configure<ClusterOptions>(options => {
                options.ClusterId = identificationOptions.ClusterId;
                options.ServiceId = identificationOptions.ServiceId;
            });

            this.builder.UseiCenterClustering(configureOptions);
            this.ConfigureLogging(build => build.AddCenterLogger(config =>
            {
                config.ProviderName = string.Format("Client_{0}", identificationOptions.ClusterId);
            }));
            return this;
        }

        public iToolClientBuilder UseAdoNetClustering(AdoNetClusterOptions clusterOptions)
        {
            this.clusterId = clusterOptions.ClusterOptions.ClusterId;
            this.builder.Configure<ClusterOptions>(options =>
            {
                options.ClusterId = clusterOptions.ClusterOptions.ClusterId;
                options.ServiceId = clusterOptions.ClusterOptions.ServiceId;
            });

            this.builder.ConfigureServices(services =>
            {
                services.AddSingleton(clusterOptions.AdoNetOptions);
                services.AddSingleton<IKeyValueStorageProvider, SQLServerKeyValueStorageProvider>();
                services.AddSingleton<IQueueStorageProvider, SQLServerQueueStorageProvider>();
            });

            this.builder.Configure<SiloMessagingOptions>(options =>
            {
                options.ResponseTimeout = clusterOptions.ResponseTimeout;
                options.ResponseTimeoutWithDebugger = clusterOptions.ResponseTimeout;
            });

            this.builder.UseAdoNetClustering(options =>
            {
                options.Invariant = Invariant;
                options.ConnectionString = clusterOptions.AdoNetOptions.GetConnection();
            });


            return this;
        }

        /// <summary>
        /// 启用 Stream
        /// 如使用 UseiCenterClustering 集群请手动注册 AdoNetOptions Service， 否则报错
        /// </summary>
        /// <param name="streamName"></param>
        /// <param name="numOfQueue"></param>
        /// <returns></returns>
        public iToolClientBuilder UseStreamProvider(string streamName, int numOfQueue = 7)
        {
            this.builder.UseStreams(streamName, options =>
            {
                options.ConfigurePartitioning(numOfQueue);
            });

            return this;
        }

        public iToolClientBuilder ConfigureLogging(Action<ILoggingBuilder> configureLogging)
        {
            this.builder.ConfigureLogging(configureLogging);
            return this;
        }

        public async Task<iToolClusterHostClient> BuildAndConnectAsync(Action anavailable = null)
        {
            try
            {
                this.anavailable = anavailable;
                var client = this.builder.Build();
                await client.Connect(RetryFilter);
                return new ClusterHostClient(client, this.clusterId);
            }
            catch (Exception)
            {
                if (anavailable != null)
                {
                    anavailable.Invoke();
                    return default;
                }
                else
                {
                    throw;
                }
            }
        }

        private iToolClientBuilder AddFromAppDomain()
        {
            this.builder.ConfigureApplicationParts(parts => parts.AddFromApplicationBaseDirectory().WithCodeGeneration().WithReferences());
            return this;
        }

        private async Task<bool> RetryFilter(Exception exception)
        {
            if (exception.GetType() != typeof(SiloUnavailableException))
            {
                Console.WriteLine($"Cluster client failed to connect to cluster with unexpected error.  Exception: {exception}");
                if (this.anavailable != null)
                {
                    this.anavailable.Invoke();
                }
                if (attempt > InitializeAttemptsBeforeFailing)
                    return false;

                attempt += 10;
            }
            attempt++;
            Console.WriteLine($"Cluster client attempt {attempt} of {InitializeAttemptsBeforeFailing} failed to connect to cluster.  Exception: {exception}");
            if (attempt > InitializeAttemptsBeforeFailing)
            {
                return false;
            }
            await Task.Delay(TimeSpan.FromSeconds(1));
            return true;
        }
    }
}
